#!/bin/sh
#
# build
#
# Build script for the Dragora GNU/Linux-Libre website
# (https://www.dragora.org)
#
#
# Copyright (C) 2020 Michael Siegel
#      
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#     
#   http://www.apache.org/licenses/LICENSE-2.0
#              
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License. 


#### "CONSTANTS" ####

PROGNAME=build
TOOLBOX='cat cmp cut find grep head mkdir sed sort stat rm rsync wc'

## Source directories and files
SOURCE_DIR=source
COMMON_DIR="$SOURCE_DIR"/common
PAGES_DIR="$SOURCE_DIR"/pages
PAGES_DIR_MASTER="$PAGES_DIR"/en
TMP_DIR="$SOURCE_DIR"/tmp
FOOTER_PARAMS=footer_params
HEADER_PARAMS=header_params
INDEX_PAGE=index.html.in

## Common directories and files
CSS_DIR=css
STYLESHEET=main.css
STYLESHEET_PATH="$CSS_DIR/$STYLESHEET"
IMG_DIR=img
FAVICON=dragora.ico
FAVICON_PATH="$IMG_DIR/$FAVICON"
LOGO=dragora_logo.png
LOGO_PATH="$IMG_DIR/$LOGO"

## Output directory
OUTPUT_DIR=output

## Misc
COPYRIGHT_HOLDERS='The Dragora Team'
COPYRIGHT_YEARS='2019, 2020'
DIR_UP='../'
SEPARATOR='|'  # Used in <title></title>.
SITE_TITLE=Dragora
TZ=UTC


#### GLOBAL VARIABLES ####  

backpath=
css_path=
err_msg=
favicon_path=
lang=
logo_path=
mod_date=
page_title=
pg=
pg_basename_in=
pg_basename_out=
pg_path_in=
pg_path_out=
sublevel=
title=


#### FUNCTIONS ####

_abort() {
  ## Run _cleanup if "$TMP_DIR" exists and exit the script with a return code
  ## indicating an error

  [ -d "$TMP_DIR" ] && _cleanup
  exit 1
}

_cleanup() {
  ## Remove any temporary files

  rm -rf "${TMP_DIR:?}"/*
}

_get_lang_dirs() {
  ## Find all language-specific page directories

  find "$PAGES_DIR" -maxdepth 1 -type d -not -name '.*' -name '??' | \
    sed 's/^.*\///'
}

_get_page_title() {
  ## Retrieve page title from input file

  grep '^<!--PAGETITLE:.*-->$' "$1" | cut -d ':' -f 2 | sed 's/-->$//'
}

_get_regular_pages() {
  ## Find all HTML pages in "$PAGES_DIR"/"$lang", except those that belong to
  ## the Dragora Handbook

  find "$PAGES_DIR"/"$lang" -path "$PAGES_DIR"/"$lang"/doc/manual -prune -o \
    -type f -name '*.html.in' -print
}

_mk_header() {
  ## Compile HTML page header

  printf '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN"\n  "http://www.w3.org/TR/html4/strict.dtd">\n'
  printf '<html lang="%s">\n<head>\n' "$lang"
  printf '  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">\n'
  printf '  <link rel="stylesheet" type="text/css" href="%s">\n' "$css_path"
  printf '  <link rel="icon" type="image/gif" href="%s">\n' "$favicon_path"
  printf '  <title>%s</title>\n' "$title"
  printf '  <meta name="description" content="%s">\n' "$lang_description"
  printf '</head>\n<body>\n'
  printf '    <div class="header" role="banner">\n'
  printf '      <img src="%s" height="80" alt="Dragora logo">\n' "$logo_path"
  printf '      <div class="header_text">\n'
  printf '        <h1>Dragora</h1>\n'
  printf '        <p class="tagline">%s</p>\n' "$lang_tagline"
  printf '      </div>  <!-- close header_text -->\n'
  printf '    </div>  <!-- close header -->\n'
  printf '  <div class="torso">\n'
}

_mk_nav() {
  ## Compile HTML navigation menu
  
  printf '      <div class="site_nav" role="navigation">\n'
  printf '        <ul>\n'
  printf '          <li><div class="site_nav_item"><a href="%s/">%s</a></div></li>\n' "${DIR_UP}${backpath}${lang}"  "$(_get_page_title "${PAGES_DIR}/${lang}/index.html.in")"
  printf '          <li><div class="site_nav_item"><a href="%snews/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/news/index.html.in")"
  printf '          <li><div class="site_nav_item"><a href="%sget/">%s</a></div>\n' "${backpath%}" "$(_get_page_title "${PAGES_DIR}/${lang}/get/index.html.in")"
    if [ "$pg_branch" = get ]
    then
      printf '            <ul>\n'
      printf '              <li><div class="site_nav_item"><a href="%sget/mirrors/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/get/mirrors/index.html.in")"
      printf '            </ul>\n'
    fi
  printf '          </li>\n'
  printf '          <li><div class="site_nav_item"><a href="%sdoc/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/doc/index.html.in")"
  printf '          <li><div class="site_nav_item"><a href="%scontribute/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/contribute/index.html.in")"
  printf '          <li><div class="site_nav_item"><a href="%scommunity/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/community/index.html.in")"
  printf '          <li><div class="site_nav_item"><a href="%sreport_bug/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/report_bug/index.html.in")"
  printf '          <li><div class="site_nav_item"><a href="%sdevel/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/devel/index.html.in")"
  printf '          <li><div class="site_nav_item"><a href="%stestbed/">%s</a></div>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/testbed/index.html.in")"
    if [ "$pg_branch" = testbed ]
    then
      printf '            <ul>\n'
      printf '              <li><div class="site_nav_item"><a href="%stestbed/coffee/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/testbed/coffee/index.html.in")"
      printf '              <li><div class="site_nav_item"><a href="%stestbed/icecream/">%s</a></div></li>\n' "${backpath}" "$(_get_page_title "${PAGES_DIR}/${lang}/testbed/icecream/index.html.in")"
      printf '            </ul>\n'
    fi
  printf '          </li>\n'
  printf '        </ul>\n'
  printf '      </div> <!-- close site_nav -->\n'
  printf '      <div class="main" role="main">\n'
}

_mk_footer() {
  ## Compile HTML page footer

  printf '\n        <p class="back_top"><a href="#">%s</a></p>\n' "$lang_back_to_top"
  printf '      </div> <!-- close main -->\n'
  printf '  </div> <!-- close torso -->\n'
  printf '    <hr class="footer_hr">\n'
  printf '    <div class="footer" role="contentinfo">\n'
  printf '      <p>%s: %s</p>\n' "$lang_mod_label" "$mod_date"
  printf '      <p>Copyright © %s %s<br>\n' "$COPYRIGHT_YEARS" "$COPYRIGHT_HOLDERS"
  printf '        This work is licensed under a\n'
  printf '        <a href="https://creativecommons.org/licenses/by-sa/4.0/">\n'
  printf '        Creative Commons Attribution-ShareAlike 4.0 International\n'
  printf '        License</a>.\n'
  printf '      </p>\n'
  printf '    </div> <!-- close footer -->\n'
  printf '</body>\n</html>\n'
}

_perr() {
  ## Print a message to stderr

  printf '%s\n' "$PROGNAME: $1" >&2
}

_probe_dirs_files() {
  ## Probe for existence of needed directories and files

  # Printing error messages right away and only returning at the end because we
  # want to catch all missing files and directories at once.

  # Local variables
  d=
  sd=
  err=0

  # Probe for top-level source directory
  if [ ! -d "$SOURCE_DIR" ]
  then
     _perr "directory not found -- '$SOURCE_DIR'"
     err=1
  else
    # Probe for sub-directories and files
    for d in "$COMMON_DIR" "$PAGES_DIR"
    do
      if [ ! -d "$d" ]
      then
        _perr "directory not found -- '$d'"
        err=1
        continue
      fi
      case "$d" in
        "$COMMON_DIR")
          for sd in "$d/$CSS_DIR" "$d/$IMG_DIR"
          do
            if [ ! -d "$sd" ]
            then
              _perr "directory not found -- '$sd'"
              err=1
              continue
            fi
            case "$sd" in
              "$d/$CSS_DIR")
                f="$d/$CSS_DIR/$STYLESHEET"
                [ -f "$f" ] || { _perr "stylesheet not found -- '$f'"; err=1; }
              ;;
              "$d/$IMG_DIR")
                f="$d/$IMG_DIR/$FAVICON"
                [ -f "$f" ] || { _perr "favicon not found -- '$f'"; err=1; }
  
                f="$d/$IMG_DIR/$LOGO"
                [ -f "$f" ] || { _perr "logo not found -- '$f'"; err=1; }
              ;;
            esac
          done
        ;;
        "$PAGES_DIR")
          [ -z "$(find "$PAGES_DIR" -mindepth 1 -maxdepth 1)" ] && \
            { _perr "directory must not be empty -- '$PAGES_DIR'"; err=1; }
          if [ ! -d "$PAGES_DIR_MASTER" ]
          then
            _perr "master page directory not found -- '$PAGES_DIR_MASTER'"
            err=1
          else
            # Check whether master tree is basically sane
            for f in "$HEADER_PARAMS" "$FOOTER_PARAMS" "$INDEX_PAGE"
            do
              f="$PAGES_DIR_MASTER/$f"
              [ -f "$f" ] || { _perr "file not found -- '$f'"; err=1; }
              # Improve error message. 
            done
            # Determine if subtrees of all page directories are identical to
            # master subtree.
            find "$PAGES_DIR_MASTER" -printf '%P\n' | sort \
              > "$TMP_DIR"/treemaster
            for pd in $(_get_lang_dirs)
            do
              find "$PAGES_DIR/$pd" -printf '%P\n' | sort \
                > "$TMP_DIR"/treecopy
              cmp -s "$TMP_DIR"/treemaster "$TMP_DIR"/treecopy || { _perr \
                "'$PAGES_DIR/$pd': subtree not identical to '$PAGES_DIR_MASTER'";
                  err=1; }
                # Improve error message
            done
          fi
        ;;
      esac
    done
  fi
    
  [ "$err" -gt 0 ] && { unset d sd f pd err; return 1; }
  unset d sd f pd err
  return 0
}

_probe_toolbox() {
  ## Probe for external commands

  err_msg=
  cmd=
  cmd_first_char=

  for cmd in $TOOLBOX
  do
    command -v "$cmd" >/dev/null || \
      { err_msg="command not found -- '$cmd'"; break; }
    cmd_first_char="$(printf '%s' "$cmd" | cut -c 1)"
    if [ "$cmd_first_char" = '/' ]
    then
      [ -x "$cmd" ] || \
        { err_msg="command found, but not executable -- '$cmd'"; break; }
    fi
  done
  unset cmd cmd_first_char
  
  [ -n "$err_msg" ] && return 1
  

  # Find GNU version commands that need it
  
  for cmd in $TOOLBOX
  do
    case "$cmd" in
      find)
        is_gnu="$(find --version | head -n1 | \
          sed 's/.*\(GNU findutils\).*/\1/')"
        [ -n "$is_gnu" ] || { err_msg="GNU version of '$cmd' required"; break; }
      ;;
      stat)
        is_gnu="$(stat --version | head -n1 | \
          sed 's/.*\(GNU coreutils\).*/\1/')"
        [ -n "$is_gnu" ] || { err_msg="GNU version of '$cmd' required"; break; }
      ;;
    esac
  done
  unset cmd is_gnu
  
  [ -n "$err_msg" ] && return 1
  return 0
}

_set_backpath() {

  backpath=  # If you don't, the string will grow with every invocation.

  while [ "$sublevel" -gt 0 ]
  do
    backpath="${backpath}$DIR_UP"
    sublevel="$((sublevel-1))"
  done

}


#### MAIN ####

# TODO: implement traps


## Environment checks

_probe_toolbox || { _perr "$err_msg"; _abort; }
_probe_dirs_files || { _cleanup; _abort; }

mkdir -p "$TMP_DIR" "$OUTPUT_DIR" || _abort


# Sync static common files

rsync -av "$COMMON_DIR"/ "$OUTPUT_DIR" || _abort 


# Compile pages

for lang in $(_get_lang_dirs)
do
  # Get values for language-specific parameters in header and footer

  . "$PAGES_DIR/$lang/$HEADER_PARAMS" || _abort
  . "$PAGES_DIR/$lang/$FOOTER_PARAMS" || _abort


  # Compile pages

  for pg in $(_get_regular_pages)
  do
    # Set path and file names

    pg_path_in="${pg%/*}"
    pg_path_out="$(printf '%s' "$pg_path_in" | sed "s,$PAGES_DIR/,,")"
    pg_branch="$(printf '%s' "$pg_path_in" | \
      sed -e "s,$PAGES_DIR/$lang,," -e 's,/,,' -e 's,/.*,,')"
    # The language-specific index page is not in any branch (i.e., $pg_path_in
    # has no trailing slash in this case). The 'sed' command above ensures that
    # 'branch' will be empty for the index page but have a meaningful value
    # otherwise.
    pg_basename_in="${pg##*/}"
    pg_basename_out="${pg_basename_in%.in}"  # .html.in → .html

    sublevel="$(printf '%s' "$pg_path_out" | sed 's/[^\/]//g' | wc -m)"
    _set_backpath

    # these should, maybe, be 'local' to _mk_header
    css_path="${DIR_UP}${backpath}${STYLESHEET_PATH}"
    favicon_path="${DIR_UP}${backpath}${FAVICON_PATH}"
    logo_path="${DIR_UP}${backpath}${LOGO_PATH}"

    # Determine page title
      
    page_title="$(_get_page_title "$pg")"
    
    title="$SITE_TITLE $SEPARATOR $page_title"

    # Determine modification date (requires GNU version of `stat')

    mod_date="$(TZ="$TZ" stat -c '%y' "$pg" | cut -d '.' -f 1) $TZ"

    # Action
    
    mkdir -p "$OUTPUT_DIR"/"$pg_path_out" || _abort

    _mk_header > "$TMP_DIR"/header.html.in || _abort
    _mk_nav "$pg_path_in" > "$TMP_DIR/nav.html.in" || _abort
    _mk_footer > "$TMP_DIR"/footer.html.in || _abort
    cat "$TMP_DIR"/header.html.in "$TMP_DIR"/nav.html.in "$pg" \
      "$TMP_DIR"/footer.html.in \
      > "$OUTPUT_DIR"/"$pg_path_out"/"$pg_basename_out" || _abort
  done
done
_cleanup
